package com.derbysoft.synxisadapter.dswitch3.translator;

import com.derbysoft.core.utils.DateTimeUtils;
import com.derbysoft.dswitch.dto.hotel.avail.*;
import com.derbysoft.dswitch.dto.hotel.common.CancelPolicyDTO;
import com.derbysoft.dswitch.dto.hotel.common.ChargeItemDTO;
import com.derbysoft.dswitch.dto.hotel.common.PaymentType;
import com.derbysoft.dswitch.dto.hotel.common.StayDateRangeDTO;
import com.derbysoft.dswitch.remote.hotel.dto.ErrorDTO;
import com.derbysoft.dswitch.remote.hotel.dto.HotelAvailRequest;
import com.derbysoft.dswitch.remote.hotel.dto.HotelAvailResponse;
import com.derbysoft.synxisadapter.commons.dto.synxis.*;
import com.derbysoft.synxisadapter.commons.exception.CurrencyResolvedException;
import com.derbysoft.synxisadapter.commons.model.Channel;
import com.derbysoft.synxisadapter.commons.model.TaxPolicy;
import com.derbysoft.synxisadapter.commons.utils.Constant;
import com.derbysoft.synxisadapter.dswitch2.translator.Translator;
import com.derbysoft.synxisadapter.dswitch2.translator.impl.TranslateHelper;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * User: jason
 * Date: 2012-11-22
 */
@Component("ds3AvailRSTranslator")
public class HotelAvailRSTranslator extends AbstractTranslator implements Translator<OTAHotelAvailRS, HotelAvailResponse, HotelAvailRequest> {
    private static final int TAX_SCALE = 4;

    @Override
    public HotelAvailResponse translate(OTAHotelAvailRS otaHotelAvailRS, HotelAvailRequest request) {
        HotelAvailResponse response = new HotelAvailResponse();
        response.setTaskId(request.getHeader().getTaskId());
        HotelAvailRS hotelAvailRS = new HotelAvailRS();
        if (isFailed(otaHotelAvailRS)) {
            List<String> hotels = request.getHotelAvailRQ().getAvailCriteria().getHotelCodesList();
            hotelAvailRS.setHotelAvailErrorsList(createAvailErrors(hotels, otaHotelAvailRS.getErrors().getError()));
            response.setError(createErrorDTO(otaHotelAvailRS));
        } else {
            hotelAvailRS.setHotelAvailRoomStaysList(createRoomStays(request, otaHotelAvailRS.getRoomStays()));
            hotelAvailRS.setNoAvailHotelsList(findNoAvailHotels(request.getHotelAvailRQ().getAvailCriteria().getHotelCodesList(), hotelAvailRS));
        }
        response.setHotelAvailRS(hotelAvailRS);
        return response;
    }

    private List<String> findNoAvailHotels(List<String> hotels, HotelAvailRS hotelAvailRS) {
        ArrayList<String> noAvailHotels = new ArrayList<String>();
        for (String hotelCode : hotels) {
            if (noHotelAvail(hotelCode, hotelAvailRS.getHotelAvailRoomStaysList())) {
                noAvailHotels.add(hotelCode);
            }
        }
        return noAvailHotels;
    }

    private boolean noHotelAvail(String hotelCode, List<HotelAvailRoomStayDTO> hotelAvailRoomStays) {
        for (HotelAvailRoomStayDTO hotelAvailRoomStay : hotelAvailRoomStays) {
            if (StringUtils.equalsIgnoreCase(hotelAvailRoomStay.getHotelCode(), hotelCode) && CollectionUtils.isNotEmpty(hotelAvailRoomStay.getRoomStaysList())) {
                return false;
            }
        }
        return true;
    }

    private List<HotelAvailRoomStayDTO> createRoomStays(HotelAvailRequest request, ArrayOfRoomStay otaRoomStays) {
        ArrayList<HotelAvailRoomStayDTO> availRoomStayDTOs = new ArrayList<HotelAvailRoomStayDTO>();
        if (otaRoomStays == null || CollectionUtils.isEmpty(otaRoomStays.getRoomStay())) {
            return availRoomStayDTOs;
        }
        Channel channel = loadChannel(request.getHeader().getSource());
        StayDateRangeDTO ds3StayDateRange = request.getHotelAvailRQ().getAvailCriteria().getStayDateRange();
        for (RoomStay roomStay : otaRoomStays.getRoomStay()) {
            if (roomStay.getRoomRates() == null || CollectionUtils.isEmpty(roomStay.getRoomRates().getRoomRate())) {
                continue;
            }
            Map<String, RatePlanDTO> ds3RatePlanMap = createDS3RatePlanMap(roomStay.getRatePlans());
            Map<String, RoomTypePair> ds3RoomTypePairMap = createDS3RoomTypeMap(roomStay.getRoomTypes());
            ArrayList<AvailRoomStayDTO> ds3RoomStayDTOs = new ArrayList<AvailRoomStayDTO>();
            for (RoomRate roomRate : roomStay.getRoomRates().getRoomRate()) {
                RatePlanDTO ds3RatePlan = ds3RatePlanMap.get(roomRate.getRatePlanCode());
                RoomTypePair roomTypePair = ds3RoomTypePairMap.get(roomRate.getRoomTypeCode());
                if (ds3RatePlan == null || roomTypePair == null) {
                    continue;
                }
                AvailRoomStayDTO ds3RoomStay = new AvailRoomStayDTO();
                ds3RoomStay.setRatePlan(calcTax(channel, ds3StayDateRange, roomRate, ds3RatePlan));
                ds3RoomStay.setRoomType(roomTypePair.getRoomTypeDTO());
                ds3RoomStay.setRoomRate(createDS3RoomRate(roomRate.getRates(), channel));
                Integer numberOfUnits = request.getHotelAvailRQ().getAvailCriteria().getRoomStayCandidate().getNumberOfUnits();
                if(numberOfUnits==null || numberOfUnits - roomTypePair.getRoomType().getNumberOfUnits() > 0){
                    ds3RoomStay.setQuantity(1);
                }else {
                    ds3RoomStay.setQuantity(roomTypePair.getRoomType().getNumberOfUnits());
                }
                ds3RoomStay.setLanguage(Constant.EN_US);
                ds3RoomStay.setCurrency(getCurrency(roomRate));
                ds3RoomStayDTOs.add(ds3RoomStay);
            }
            HotelAvailRoomStayDTO availRoomStayDTO = new HotelAvailRoomStayDTO();
            availRoomStayDTO.setHotelCode(roomStay.getBasicPropertyInfo().getHotelCode());
            availRoomStayDTO.setRoomStaysList(ds3RoomStayDTOs);
            availRoomStayDTOs.add(availRoomStayDTO);
        }
        return availRoomStayDTOs;
    }

    private String getCurrency(RoomRate roomRate) {
        if (roomRate.getRates() != null && CollectionUtils.isNotEmpty(roomRate.getRates().getRate())) {
            RateType rateType = roomRate.getRates().getRate().get(0);
            return rateType.getBase().getCurrencyCode();
        }
        throw new CurrencyResolvedException();
    }

    private Map<String, RoomTypePair> createDS3RoomTypeMap(ArrayOfRoomType roomTypes) {
        Map<String, RoomTypePair> roomTypePairMap = new HashMap<String, RoomTypePair>();
        if (roomTypes == null || CollectionUtils.isEmpty(roomTypes.getRoomType())) {
            return roomTypePairMap;
        }
        for (RoomType roomType : roomTypes.getRoomType()) {
            RoomTypeDTO roomTypeDTO = new RoomTypeDTO();
            roomTypeDTO.setCode(roomType.getRoomTypeCode());
            roomTypeDTO.setName(roomType.getRoomDescription().getName());
            if (roomType.getRoomDescription() != null) {
                roomTypeDTO.setDescription(roomType.getRoomDescription().getText());
            }
            roomTypePairMap.put(roomType.getRoomTypeCode(), new RoomTypePair(roomType, roomTypeDTO));
        }
        return roomTypePairMap;
    }

    private Map<String, RatePlanDTO> createDS3RatePlanMap(ArrayOfRatePlan ratePlans) {
        HashMap<String, RatePlanDTO> ratePlanDTOMap = new HashMap<String, RatePlanDTO>();
        if (ratePlans == null || CollectionUtils.isEmpty(ratePlans.getRatePlan())) {
            return ratePlanDTOMap;
        }
        for (RatePlan ratePlan : ratePlans.getRatePlan()) {
            RatePlanDTO ratePlanDTO = new RatePlanDTO();
            ratePlanDTO.setCode(ratePlan.getRatePlanCode());
            ratePlanDTO.setName(ratePlan.getRatePlanName());
            ratePlanDTO.setPaymentType(PaymentType.POA);

            CancelPolicyDTO cancelPolicy = translateCancelPolicy(ratePlan);
            if (cancelPolicy != null) {
                ratePlanDTO.setCancelPolicy(cancelPolicy);
            }
            FreeMealDTO freeMealDTO = new FreeMealDTO();
            freeMealDTO.setType(FreeMealDTO.FreeMealType.NONE);
            ratePlanDTO.setFreeMeal(freeMealDTO);
            if (ratePlan.getRatePlanDescription() != null) {
                String desc = String.format("Name:[%s], Description:[%s]",
                        StringUtils.isNotBlank(ratePlan.getRatePlanDescription().getName()) ? ratePlan.getRatePlanDescription().getName() : "",
                        StringUtils.isNotBlank(ratePlan.getRatePlanDescription().getText()) ? ratePlan.getRatePlanDescription().getText() : "");
                ratePlanDTO.setDescription(desc);
            }
            ratePlanDTOMap.put(ratePlan.getRatePlanCode(), ratePlanDTO);
        }
        return ratePlanDTOMap;
    }

    private CancelPolicyDTO translateCancelPolicy(RatePlan ratePlan) {
        if (ratePlan.getCancelPenalties() != null && CollectionUtils.isNotEmpty(ratePlan.getCancelPenalties().getCancelPenalty())) {
            CancelPenalty cancelPenalty = ratePlan.getCancelPenalties().getCancelPenalty().get(0);
            CancelPolicyDTO cancelPolicyDTO = new CancelPolicyDTO();
            if (StringUtils.isNotBlank(cancelPenalty.getPolicyCode())) {
                cancelPolicyDTO.setCode(cancelPenalty.getPolicyCode());
            }
            if (cancelPenalty.getPenaltyDescription() != null && cancelPenalty.getPenaltyDescription().getText() != null) {
                cancelPolicyDTO.setDescription(cancelPenalty.getPenaltyDescription().getText());
            }
            return cancelPolicyDTO;
        }
        return null;
    }

    private RatePlanDTO calcTax(Channel channel, StayDateRangeDTO ds3StayDateRange, RoomRate roomRate, RatePlanDTO ratePlanDTO) {
        if (channel.isInclusiveTaxRate()) {
            return ratePlanDTO;
        }
        if (channel.getTaxPolicy() == TaxPolicy.PERCENT) {
            return setPercentTax(ds3StayDateRange, ratePlanDTO, roomRate);
        }
        if (channel.getTaxPolicy() == TaxPolicy.FIXED) {
            return setFixedTax(ds3StayDateRange, ratePlanDTO, roomRate);
        }
        return ratePlanDTO;
    }

    private RatePlanDTO setFixedTax(StayDateRangeDTO ds3StayDateRange, RatePlanDTO ratePlanDTO, RoomRate roomRate) {
        RateType rateType = roomRate.getRates().getRate().get(0);
        if (rateType.getTpaExtensions() != null && CollectionUtils.isNotEmpty(rateType.getTpaExtensions().getNightlyRate())) {
            BigDecimal totalPriceWithTaxAndFee = BigDecimal.ZERO;
            BigDecimal totalBasePrice = BigDecimal.ZERO;

            for (NightlyRate nightlyRate : rateType.getTpaExtensions().getNightlyRate()) {
                totalBasePrice = totalBasePrice.add(nightlyRate.getPrice());
                totalPriceWithTaxAndFee = totalPriceWithTaxAndFee.add(nightlyRate.getPriceWithTaxAndFee());
            }
            ChargeItemDTO chargeItemDTO = new ChargeItemDTO();
            chargeItemDTO.setType(ChargeItemDTO.ChargeType.FIXED);
            chargeItemDTO.setStartDate(ds3StayDateRange.getCheckin());
            chargeItemDTO.setEndDate(DateTimeUtils.formatDate(DateTimeUtils.addDays(DateTimeUtils.parse(ds3StayDateRange.getCheckout()), -1)));
            chargeItemDTO.setUnit(ChargeItemDTO.ChargeUnit.PER_ROOM_PER_STAY);

            int los = DateTimeUtils.calcLOS(DateTimeUtils.parse(ds3StayDateRange.getCheckin()), DateTimeUtils.parse(ds3StayDateRange.getCheckout()));
            BigDecimal duration = los == 0 ? BigDecimal.ONE : BigDecimal.valueOf(los);
            BigDecimal amount = totalPriceWithTaxAndFee.subtract(totalBasePrice).divide(duration, TAX_SCALE, RoundingMode.HALF_UP);
            chargeItemDTO.setValue(TranslateHelper.setScale(amount).doubleValue());
            ArrayList<ChargeItemDTO> taxes = new ArrayList<ChargeItemDTO>();
            taxes.add(chargeItemDTO);
            ratePlanDTO.setTaxesList(taxes);
        }
        return ratePlanDTO;
    }

    private RatePlanDTO setPercentTax(StayDateRangeDTO ds3StayDateRange, RatePlanDTO ratePlanDTO, RoomRate roomRate) {
        RateType rateType = roomRate.getRates().getRate().get(0);
        if (rateType.getTpaExtensions() != null && CollectionUtils.isNotEmpty(rateType.getTpaExtensions().getNightlyRate())) {
            BigDecimal totalPriceWithTaxAndFee = BigDecimal.ZERO;
            BigDecimal totalBasePrice = BigDecimal.ZERO;

            for (NightlyRate nightlyRate : rateType.getTpaExtensions().getNightlyRate()) {
                totalBasePrice = totalBasePrice.add(nightlyRate.getPrice());
                totalPriceWithTaxAndFee = totalPriceWithTaxAndFee.add(nightlyRate.getPriceWithTaxAndFee());
            }
            ChargeItemDTO chargeItemDTO = new ChargeItemDTO();
            chargeItemDTO.setType(ChargeItemDTO.ChargeType.PERCENT);
            chargeItemDTO.setStartDate(ds3StayDateRange.getCheckin());
            chargeItemDTO.setEndDate(DateTimeUtils.formatDate(DateTimeUtils.addDays(DateTimeUtils.parse(ds3StayDateRange.getCheckout()), -1)));
            chargeItemDTO.setUnit(ChargeItemDTO.ChargeUnit.PER_ROOM_PER_STAY);

            if (totalBasePrice.compareTo(BigDecimal.ZERO) == 0) {
                totalBasePrice = BigDecimal.ONE;
            }
            BigDecimal totalPercent = totalPriceWithTaxAndFee.subtract(totalBasePrice).divide(totalBasePrice, TAX_SCALE, RoundingMode.HALF_UP);
            chargeItemDTO.setValue(totalPercent.doubleValue());
            ArrayList<ChargeItemDTO> taxes = new ArrayList<ChargeItemDTO>();
            taxes.add(chargeItemDTO);
            ratePlanDTO.setTaxesList(taxes);
        }
        return ratePlanDTO;
    }

    private ErrorDTO createErrorDTO(OTAHotelAvailRS otaHotelAvailRS) {
        List<ErrorType> errorTypes = otaHotelAvailRS.getErrors().getError();
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setSource(PROVIDER_SOURCE);
        StringBuilder errorCodeBuilder = new StringBuilder();
        StringBuilder errorMsgBuilder = new StringBuilder();
        for (ErrorType errorType : errorTypes) {
            errorCodeBuilder.append(errorType.getCode()).append(";");
            errorMsgBuilder.append(String.format("Code:[%s], Msg:[%s]", errorType.getCode(), errorType.getShortText())).append(";");
        }
        errorDTO.setCode(errorCodeBuilder.toString());
        errorDTO.setMessage(errorMsgBuilder.toString());
        return errorDTO;
    }

    private List<HotelAvailErrorDTO> createAvailErrors(List<String> hotels, List<ErrorType> errorTypes) {
        ArrayList<HotelAvailErrorDTO> hotelAvailErrorDTOs = new ArrayList<HotelAvailErrorDTO>();
        for (String hotelCode : hotels) {
            for (ErrorType errorType : errorTypes) {
                HotelAvailErrorDTO availErrorDTO = new HotelAvailErrorDTO();
                availErrorDTO.setHotelCode(hotelCode);
                availErrorDTO.setErrorCode(StringUtils.isNotBlank(errorType.getCode()) ? errorType.getCode() : DEFAULT_ERROR_CODE);
                availErrorDTO.setErrorSource(PROVIDER_SOURCE);
                availErrorDTO.setErrorMsg(StringUtils.isNotBlank(errorType.getShortText()) ? errorType.getShortText() : DEFAULT_ERROR_MSG);
                hotelAvailErrorDTOs.add(availErrorDTO);
            }
        }
        return hotelAvailErrorDTOs;
    }

    private boolean isFailed(OTAHotelAvailRS otaHotelAvailRS) {
        return otaHotelAvailRS.getErrors() != null && CollectionUtils.isNotEmpty(otaHotelAvailRS.getErrors().getError());
    }

    private static final class RoomTypePair {
        private RoomType roomType;
        private RoomTypeDTO roomTypeDTO;

        private RoomTypePair(RoomType roomType, RoomTypeDTO roomTypeDTO) {
            this.roomType = roomType;
            this.roomTypeDTO = roomTypeDTO;
        }

        public RoomType getRoomType() {
            return roomType;
        }

        public void setRoomType(RoomType roomType) {
            this.roomType = roomType;
        }

        public RoomTypeDTO getRoomTypeDTO() {
            return roomTypeDTO;
        }

        public void setRoomTypeDTO(RoomTypeDTO roomTypeDTO) {
            this.roomTypeDTO = roomTypeDTO;
        }
    }
}
